<?
    /**
     * PrincipioLab
     *
     * @package    PrincipioLab
     */

    /**
     * PrincipioLab_LinkedList
     *
     * @package    PrincipioLab
     */
    class PrincipioLab_LinkedList implements IteratorAggregate, Countable
    {
        /** @var null|PrincipioLab_ListElement */
        private $root;

        /** @var null|PrincipioLab_ListElement */
        private $last;

        /** @var null|PrincipioLab_IComparator */
        private $comparator;

        /** @var int */
        private $count;


        public function __construct(PrincipioLab_IComparator $comparator = null)
        {
            $this->root = null;
            $this->last = null;
            $this->comparator = $comparator;
            $this->count = 0;

        }


        /**
         * @return PrincipioLab_LinkedList_Iterator
         */
        public function getIterator()
        {
            return new PrincipioLab_LinkedList_Iterator($this);
        }


        /**
         * @return PrincipioLab_LinkedList_ListElementIterator
         */
        public function getListElementIterator()
        {
            return new PrincipioLab_LinkedList_ListElementIterator($this);
        }


        /**
         * @return null|PrincipioLab_ListElement
         */
        public function getRootItem() { return $this->root; }


        /**
         * @return null|PrincipioLab_IDAble
         */
        public function getRoot()
        {
            $root_item = $this->getRootItem();
            return (is_null($root_item) ? null : $root_item->getValue());
        }


        /**
         * @return null|PrincipioLab_ListElement
         */
        public function getFirstItem() { return $this->getRootItem(); }


        /**
         * @return null|PrincipioLab_IDAble
         */
        public function getFirst() { return $this->getRoot(); }


        /**
         * @return null|PrincipioLab_ListElement
         */
        public function getLastItem() { return $this->last; }


        /**
         * @return null|PrincipioLab_IDAble
         */
        public function getLast()
        {
            $last_item = $this->getLastItem();
            return (is_null($last_item) ? null : $last_item->getValue());
        }


        /**
         * @return PrincipioLab_IComparator
         */
        private function getComparator()
        {
            return (is_null($this->comparator) ?  PrincipioLab_Comparator_IDAbleSingleton::getInstance() : $this->comparator);
        }


        /**
         * @param PrincipioLab_ListElement $list_element_1
         * @param PrincipioLab_ListElement $list_element_2
         *
         * @return bool
         */
        public function isEqual(PrincipioLab_ListElement $list_element_1, PrincipioLab_ListElement $list_element_2) { return ($this->getComparator()->compare($list_element_1->getValue(), $list_element_2->getValue()) == PrincipioLab_IComparator::IS_EQ); }


        /**
         * @param PrincipioLab_ListElement $list_element_1
         * @param PrincipioLab_ListElement $list_element_2
         *
         * @return bool
         */
        public function isLess(PrincipioLab_ListElement $list_element_1, PrincipioLab_ListElement $list_element_2) { return ($this->getComparator()->compare($list_element_1->getValue(), $list_element_2->getValue()) == PrincipioLab_IComparator::IS_LT); }


        /**
         * @param PrincipioLab_ListElement $list_element_1
         * @param PrincipioLab_ListElement $list_element_2
         *
         * @return bool
         */
        public function isGreater(PrincipioLab_ListElement $list_element_1, PrincipioLab_ListElement $list_element_2) { return ($this->getComparator()->compare($list_element_1->getValue(), $list_element_2->getValue()) == PrincipioLab_IComparator::IS_GT); }


        /**
         * @param PrincipioLab_ListElement $list_element_1
         * @param PrincipioLab_ListElement $list_element_2
         *
         * @return bool
         */
        public function isEqualOrLess(PrincipioLab_ListElement $list_element_1, PrincipioLab_ListElement $list_element_2)
        {
            $result = $this->getComparator()->compare($list_element_1->getValue(), $list_element_2->getValue());
            return (($result == PrincipioLab_IComparator::IS_EQ) || ($result == PrincipioLab_IComparator::IS_LT));
        }


        /**
         * @param PrincipioLab_ListElement $list_element_1
         * @param PrincipioLab_ListElement $list_element_2
         *
         * @return bool
         */
        public function isEqualOrGreater(PrincipioLab_ListElement $list_element_1, PrincipioLab_ListElement $list_element_2)
        {
            $result = $this->getComparator()->compare($list_element_1->getValue(), $list_element_2->getValue());
            return (($result == PrincipioLab_IComparator::IS_EQ) || ($result == PrincipioLab_IComparator::IS_GT));
        }


        /**
         * @param PrincipioLab_IDAble $id_able
         *
         * @return PrincipioLab_ListElement
         */
        public function make(PrincipioLab_IDAble $id_able) { return PrincipioLab_ListElement::make($id_able); }


        /**
         * Add new item to the LinkedList
         *
         * @param PrincipioLab_IDAble $id_able
         *
         * @return PrincipioLab_LinkedList
         */
        public function addItem(PrincipioLab_IDAble $id_able)
        {
            $list_element = $this->make($id_able);

            if (is_null($this->root))
            {
                $this->root = $list_element;
                $this->last = $this->root;
            }
            else
            {
                //echo "Not Root<br>";

                if ($this->isEqualOrLess($this->last, $list_element))
                {
                    $this->last->setNext($list_element);
                    $this->last = $list_element;

                    $this->count++;
                    return $this;
                };

                $cur_element = $this->root;
                $prev_element = $cur_element;

                while (!(is_null($cur_element)))
                {

                    //echo "Iteration START<br>";

                    if ($this->isEqualOrLess($list_element, $cur_element))
                    {
                        //echo "EQ OR LT<br>";

                        if ($this->root == $cur_element)
                        {
                            //echo "AS ROOT<br>";
                            $this->root = $list_element;
                            $list_element->setNext($cur_element);
                        }
                        else
                        {
                            //echo "NOT AS ROOT<br>";
                            $list_element->setNext($cur_element);
                            $prev_element->setNext($list_element);
                        };

                        $this->count++;
                        return $this;
                    };

                    $prev_element = $cur_element;
                    $cur_element = $cur_element->getNext();

                    //echo "Iteration END<br>";
                };

                //echo "REACH END<br>";
                $prev_element->setNext($list_element);
                $this->last = $list_element;
            };

            $this->count++;

            return $this;
        }


        /**
         * @param $id mixed
         *
         * @return bool|PrincipioLab_ListElement
         */
        public function getListItem($id)
        {
            /** @var PrincipioLab_ListElement $list_element */
            foreach ($this->getListElementIterator() as $list_element)
            {
                if ($list_element->_getID() == $id)
                    return $list_element;
            };

            return false;
        }


        /**
         * @param $id mixed
         *
         * @return bool|PrincipioLab_IDAble
         */
        public function getItem($id)
        {
            if (($list_element = $this->getListItem($id)) !== false)
                return $list_element->getValue();
            else
                return false;
        }


        /**
         * @param PrincipioLab_LinkedList $linked_list
         *
         * @return PrincipioLab_LinkedList
         */
        public function appendList(PrincipioLab_LinkedList $linked_list)
        {
            /** @var PrincipioLab_ListElement $list_element */
            foreach ($linked_list->getListElementIterator() as $list_element)
                $this->addItem($list_element->getValue());


            return $this;
        }


        /**
         * @return int
         */
        public function count() { return $this->count; }
    }
?>